﻿using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Utils;
using DevExpress.Persistent.Validation;
using DevExpress.Spreadsheet;
using System.Drawing;

namespace EasyXaf.ExcelImporters;

public class ImportExcelTemplate
{
    private readonly ListView _listView;
    private readonly XafApplication _application;
    private readonly IModelImporting _importingModel;

    private readonly Dictionary<string, IDictionary<bool, string>> _booleanCache = new();
    private readonly Dictionary<string, IDictionary<object, string>> _enumCache = new();

    public ImportExcelTemplate(ListView listView, XafApplication application, IModelImporting importingModel)
    {
        _listView = listView;
        _application = application;
        _importingModel = importingModel;
    }

    private string GetBooleanText(IModelImportingMember importingMemberModel, bool booleanValue)
    {
        var key = importingMemberModel.Property.Name;
        if (!_booleanCache.ContainsKey(key))
        {
            var valueTexts = new List<string>();

            if (!string.IsNullOrWhiteSpace(importingMemberModel.PredefinedValues))
            {
                valueTexts.AddRange(importingMemberModel.PredefinedValues.Split(','));
            }
            else
            {
                valueTexts.AddRange(importingMemberModel.GetBooleanValueTexts());
            }

            _booleanCache[key] = new Dictionary<bool, string>
            {
                [false] = valueTexts[0],
                [true] = valueTexts[1]
            };
        }
        return _booleanCache[key][booleanValue];
    }

    private string GetEnumText(IModelImportingMember importingMemberModel, object enumValue)
    {
        var key = importingMemberModel.Property.Name;
        if (!_enumCache.ContainsKey(key))
        {
            var enumType = importingMemberModel.GetMemberRealType();
            var enumDescriptor = new EnumDescriptor(enumType);
            var enumValues = enumDescriptor.Values;
            var enumDic = new Dictionary<object, string>();

            IList<string> enumValueTexts;
            if (!string.IsNullOrWhiteSpace(importingMemberModel.PredefinedValues))
            {
                enumValueTexts = importingMemberModel.PredefinedValues.Split(',').ToList();
            }
            else
            {
                enumValueTexts = enumType.GetEnumValueTexts().ToList();
            }

            for (var i = 0; i < enumValues.Length; i++)
            {
                enumDic[enumValues.GetValue(i)] = enumValueTexts[0];
            }

            _enumCache[key] = enumDic;
        }

        if (_enumCache[key].TryGetValue(enumValue, out var enumText))
        {
            return enumText;
        }

        return null;
    }

    private string GetDataSourceFormula(IWorkbook workbook, IModelImportingMember importingMemberModel)
    {
        var worksheetName = importingMemberModel.GetDataSourceSheetName();
        if (string.IsNullOrWhiteSpace(worksheetName))
        {
            return string.Empty;
        }

        var worksheet = workbook.Worksheets.FirstOrDefault(s => s.Name == worksheetName);
        if (worksheet == null)
        {
            var dataSource = new List<string>();
            var memberType = importingMemberModel.GetMemberRealType();

            if (importingMemberModel.IsReferenceType())
            {
                using var objectSpace = _application.CreateObjectSpace(memberType);
                dataSource.AddRange(importingMemberModel.GetDataSourceTexts(objectSpace));
            }

            if (dataSource.Any())
            {
                worksheet = workbook.Worksheets.Add(worksheetName);
                worksheet.Visible = false;

                var row = 0;
                foreach (var value in dataSource)
                {
                    worksheet.Cells[row++, 0].Value = value;
                }

                var cellRange = worksheet[$"A1:A{row}"];
                cellRange.Name = "DataSource";
            }
        }

        if (worksheet != null)
        {
            return $"'{worksheet.Name}'!$A$1:$A${worksheet["DataSource"].RowCount}";
        }

        return string.Empty;
    }

    private DataValidation CreateListDataValidation(Column sheetColumn, string dataSourceFormula)
    {
        var valueObject = ValueObjectHelper.FromValue(dataSourceFormula);
        return sheetColumn.Worksheet.DataValidations.Add(sheetColumn, DataValidationType.List, valueObject);
    }

    private DataValidation CreateRangeDataValidation(Column sheetColumn, DataValidationType validationType, string minimumValue, string maximumValue)
    {
        ValueObject critera = null;
        ValueObject critera2 = null;

        DataValidationOperator? validationOperator = null;
        string errorMessage = null;

        if (!string.IsNullOrWhiteSpace(minimumValue) && !string.IsNullOrWhiteSpace(maximumValue))
        {
            critera = ValueObjectHelper.FromValue(minimumValue);
            critera2 = ValueObjectHelper.FromValue(maximumValue);
            validationOperator = DataValidationOperator.Between;
            errorMessage = $"当前值必须介于{minimumValue}与{maximumValue}之间";
        }
        else if (!string.IsNullOrWhiteSpace(minimumValue))
        {
            critera = ValueObjectHelper.FromValue(minimumValue);
            validationOperator = DataValidationOperator.GreaterThanOrEqual;
            errorMessage = $"当前值必须大于或等于{minimumValue}";
        }
        else if (!string.IsNullOrWhiteSpace(maximumValue))
        {
            critera = ValueObjectHelper.FromValue(maximumValue);
            validationOperator = DataValidationOperator.LessThanOrEqual;
            errorMessage = $"当前值必须小于或等于{maximumValue}";
        }

        DataValidation dataValidation = null;

        if (validationOperator.HasValue)
        {
            if (validationOperator.Value == DataValidationOperator.Between)
            {
                dataValidation = sheetColumn.Worksheet.DataValidations.Add(sheetColumn, validationType, validationOperator.Value, critera, critera2);
            }
            else
            {
                dataValidation = sheetColumn.Worksheet.DataValidations.Add(sheetColumn, validationType, validationOperator.Value, critera);
            }

            dataValidation.ErrorMessage = errorMessage;
        }

        return dataValidation;
    }

    private DataValidation CreateRangeDataValidation(Column sheetColumn, IModelImportingMember importingMemberModel)
    {
        var minimumValue = string.Empty;
        var maximumValue = string.Empty;

        if (!string.IsNullOrWhiteSpace(importingMemberModel.MinimumValue) || !string.IsNullOrWhiteSpace(importingMemberModel.MaximumValue))
        {
            minimumValue = importingMemberModel.MinimumValue;
            maximumValue = importingMemberModel.MaximumValue;
        }
        else
        {
            var rangeAttribute = importingMemberModel.Property.MemberInfo.FindAttribute<RuleRangeAttribute>();
            if (rangeAttribute != null)
            {
                minimumValue = ((IRuleRangeProperties)rangeAttribute).MinimumValue?.ToString();
                maximumValue = ((IRuleRangeProperties)rangeAttribute).MaximumValue?.ToString();
            }
        }

        if (!string.IsNullOrWhiteSpace(minimumValue) || !string.IsNullOrWhiteSpace(maximumValue))
        {
            DataValidationType? validationType = null;
            var memberType = importingMemberModel.GetMemberRealType();
            if (memberType.IsNumericType())
            {
                if (memberType.IsWholeNumberType())
                {
                    validationType = DataValidationType.WholeNumber;
                }
                else
                {
                    validationType = DataValidationType.Decimal;
                }
            }
            else if (memberType == typeof(DateTime))
            {
                validationType = DataValidationType.Date;
            }

            if (validationType.HasValue)
            {
                return CreateRangeDataValidation(sheetColumn, validationType.Value, minimumValue, maximumValue);
            }
        }

        return null;
    }

    private void CreateDataValidation(Column sheetColumn, IModelImportingMember importingMemberModel)
    {
        DataValidation dataValidation = null;

        var memberType = importingMemberModel.GetMemberRealType();

        if (!string.IsNullOrWhiteSpace(importingMemberModel.PredefinedValues))
        {
            dataValidation = CreateListDataValidation(sheetColumn, importingMemberModel.PredefinedValues);
        }
        else if (memberType.IsNumericType() || memberType == typeof(DateTime))
        {
            dataValidation = CreateRangeDataValidation(sheetColumn, importingMemberModel);
        }
        else if (memberType == typeof(bool))
        {
            dataValidation = CreateListDataValidation(sheetColumn, string.Join(",", importingMemberModel.GetBooleanValueTexts()));
        }
        else if (memberType.IsEnum)
        {
            dataValidation = CreateListDataValidation(sheetColumn, string.Join(",", memberType.GetEnumValueTexts()));
        }
        else
        {
            var dataSourceFormula = GetDataSourceFormula(sheetColumn.Worksheet.Workbook, importingMemberModel);
            if (!string.IsNullOrWhiteSpace(dataSourceFormula))
            {
                dataValidation = CreateListDataValidation(sheetColumn, $"={dataSourceFormula}");
            }
        }

        if (dataValidation != null)
        {
            if (importingMemberModel.IgnoreValidationError)
            {
                dataValidation.ShowErrorMessage = false;
            }

            if (importingMemberModel.IsRequired())
            {
                dataValidation.AllowBlank = false;
            }
        }
    }

    private void ExportViewData(ListView listView, Worksheet worksheet, int startRow, int startColumn, IEnumerable<IModelImportingMember> importingMemberModels)
    {
        foreach (var obj in listView.CollectionSource.GetEnumerable())
        {
            int column = startColumn;
            foreach (var importingMemberModel in importingMemberModels)
            {
                var sheetCell = worksheet[startRow, column];
                var memberType = importingMemberModel.GetMemberRealType();
                var value = importingMemberModel.Property.MemberInfo.GetValue(obj);
                if (value != null)
                {
                    if (memberType.IsEnum)
                    {
                        sheetCell.SetValue(GetEnumText(importingMemberModel, value));
                    }
                    else if (memberType == typeof(bool))
                    {
                        sheetCell.SetValue(GetBooleanText(importingMemberModel, (bool)value));
                    }
                    else if (memberType == typeof(Guid))
                    {
                        sheetCell.SetValue(value.ToString());
                    }
                    else if (importingMemberModel.IsReferenceType())
                    {
                        var defaultMember = importingMemberModel.Property.MemberInfo.MemberTypeInfo.DefaultMember;
                        sheetCell.SetValue(defaultMember.GetValue(value));
                    }
                    else if (importingMemberModel.IsImageType())
                    {
                        var stream = new MemoryStream((byte[])value);
                        var imageSource = SpreadsheetImageSource.FromStream(stream);
                        worksheet.Pictures.AddPicture(imageSource, sheetCell, true);
                    }
                    else
                    {
                        sheetCell.SetValue(value);
                    }
                }
                column++;
            }
            startRow++;
        }
    }

    public Stream GenerateTemplate()
    {
        using var workbook = new Workbook();

        var worksheet = workbook.Worksheets.First();
        worksheet.Name = _importingModel.GetCaption();

        var startRow = _importingModel.StartRow;
        var startColumn = _importingModel.StartColumn;
        var sheetColumnIndex = startColumn;
        var importingMemberModels = _importingModel.Members
            .Where(x => x.VisibleInTemplate)
            .OrderBy(x => x.ColumnIndex)
            .ThenBy(x => x.Index)
            .ToList();

        foreach (var importingMemberModel in importingMemberModels)
        {
            var memberType = importingMemberModel.GetMemberRealType();
            var sheetColumn = worksheet.Columns[sheetColumnIndex];
            var sheetCell = worksheet.Cells[startRow, sheetColumnIndex++];
            sheetCell.Value = importingMemberModel.GetCaption();
            sheetCell.Font.Bold = importingMemberModel.IsRequired();
            sheetColumn.Visible = importingMemberModel.ColumnVisible;

            if (importingMemberModel.HeaderFontColor.HasValue)
            {
                sheetCell.Font.Color = importingMemberModel.HeaderFontColor.Value;
            }
            else if (_importingModel.HeaderFontColor.HasValue)
            {
                sheetCell.Font.Color = _importingModel.HeaderFontColor.Value;
            }

            if (importingMemberModel.HeaderBackgroundColor.HasValue)
            {
                sheetCell.FillColor = importingMemberModel.HeaderBackgroundColor.Value;
            }
            else if (_importingModel.HeaderBackgroundColor.HasValue)
            {
                sheetCell.FillColor = _importingModel.HeaderBackgroundColor.Value;
            }
            else
            {
                sheetCell.FillColor = Color.FromArgb(0xCC, 0xCC, 0xCC);
            }

            if (importingMemberModel.ColumnWidth.HasValue)
            {
                sheetColumn.ColumnWidthInCharacters = importingMemberModel.ColumnWidth.Value;
            }

            if (!string.IsNullOrWhiteSpace(importingMemberModel.NumberFormat))
            {
                sheetColumn.NumberFormat = importingMemberModel.NumberFormat;
            }

            CreateDataValidation(sheetColumn, importingMemberModel);
        }

        if (_importingModel.IsExportViewData)
        {
            ExportViewData(_listView, worksheet, startRow + 1, startColumn, importingMemberModels);
        }

        var stream = new MemoryStream();
        workbook.SaveDocument(stream, DocumentFormat.OpenXml);
        return stream;
    }
}
